﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using GeoFramework;

namespace MapControl
{
    internal class CommonTilesManager : ITilesManager, IDisposable
    {
        private List<MapTile> tiles = new List<MapTile>();
        private List<MapTile> visibleTiles = new List<MapTile>();
        private Map map;
        private Size mapSize;
        int currentZoomLevel = -1;
        MapType currentMapType = MapType.Roadmap;
        IProjectionCalculator projectionCalculator;
        IImageManager imgManager;

        public IProjectionCalculator ProjectionCalculator
        {
            get { return projectionCalculator; }
        }

        private struct PointPositionPair
        {
            public Point Point { get; set; }
            public Position Position { get; set; }
        }

        public CommonTilesManager(Map map, IImageManager imgManager)
        {
            this.map = map;
            projectionCalculator = new CommonProjectionCalculator();
            this.imgManager = imgManager;
            this.imgManager.ImageSetted += new EventHandler(imgManager_ImageSetted);
            this.mapSize = map.Size;
        }

        void imgManager_ImageSetted(object sender, EventArgs e)
        {
            try
            {
                if (map.InvokeRequired)
                    map.Invoke(new Action(map.Refresh));
                else
                    map.Refresh();
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex);
            }
        }

        #region ITilesManager Members

        public IEnumerable<MapTile> Tiles
        {
            get
            {
                return tiles;
            }
        }

        public IEnumerable<MapTile> VisibleTiles
        {
            get
            {
                return visibleTiles;
            }
        }

        public void RecalculateVisibleTiles(System.Drawing.Point pxVector, int zoomLevel, MapType mapType, bool forceClearTiles)
        {
            if (pxVector.IsEmpty && zoomLevel == currentZoomLevel && mapType == currentMapType && !forceClearTiles)// mapSize.Equals(map.Size))
                return;
            if (currentZoomLevel != zoomLevel || currentMapType != mapType || forceClearTiles)//!mapSize.Equals(map.Size))
            {
                currentZoomLevel = zoomLevel;
                currentMapType = mapType;
                foreach (var item in tiles)
                {
                    item.Dispose();
                }
                tiles.Clear();
                //mapSize = map.Size;
            }
            if (tiles.Count > 0)
            {
                foreach (var item in tiles)
                {
                    item.Middle = new System.Drawing.Point(item.Middle.X + pxVector.X, item.Middle.Y + pxVector.Y);
                }
            }
            else
                tiles.Add(new MapTile(new Size(256, 256), map.Center, currentZoomLevel, currentMapType, imgManager) { Middle = new Point(map.Width / 2, map.Height / 2) });

            //visibleTiles.Clear();
            List<MapTile> vt = new List<MapTile>();
            CalculateVisibleParts(vt, new List<MapTile>(tiles));
            visibleTiles = vt;
        }

        private void CalculateVisibleParts(List<MapTile> vt, List<MapTile> tiles)
        {
            //List<MapTile> visibleParts = new List<MapTile>();
            PointD fromGeoPositon;
            //Point point;
            List<Point> usedPoints = new List<Point>();
            List<MapTile> toRecalculate = new List<MapTile>();
            bool needCalculate = false;
            //MapTile newPart;

            foreach (var item in tiles)
            {
                CheckVisible(item);
                //System.Diagnostics.Debug.WriteLine(string.Format("Visible for rectangle x={0}, y={1}, width={2}, height={3}: {4}", item.Area.X, item.Area.Y, item.Area.Width, item.Area.Height, item.IsVisible));
                if (item.IsVisible)
                {
                    vt.Add(item);
                    item.LoadImage();
                    // neighbours
                    fromGeoPositon = projectionCalculator.FromPosition(item.Position, currentZoomLevel);
                    //left
                    if (item.VisibleAreaOnMap.X > 0)
                    {
                        CheckTile(item.Middle.X - 256, item.Middle.Y, new PointD(fromGeoPositon.X - 256, fromGeoPositon.Y), usedPoints, toRecalculate, ref needCalculate, item);
                    }
                    // right
                    if (item.Area.X + 256 < map.Width)
                    {
                        CheckTile(item.Middle.X + 256, item.Middle.Y, new PointD(fromGeoPositon.X + 256, fromGeoPositon.Y), usedPoints, toRecalculate, ref needCalculate, item);
                    }
                    // top
                    if (item.VisibleAreaOnMap.Y > 0)
                    {
                        CheckTile(item.Middle.X, item.Middle.Y - 256, new PointD(fromGeoPositon.X, fromGeoPositon.Y - 256), usedPoints, toRecalculate, ref needCalculate, item);
                    }
                    // bottom
                    if (item.Area.Y + 256 < map.Height)
                    {
                        CheckTile(item.Middle.X, item.Middle.Y + 256, new PointD(fromGeoPositon.X, fromGeoPositon.Y + 256), usedPoints, toRecalculate, ref needCalculate, item);
                    }
                }
                else
                {
                    if (item.Middle.X < -256 || item.Middle.X > 256 + map.Width || item.Middle.Y < -256 || item.Middle.Y > 256 + map.Height)
                        item.DisposeImage();
                    //else
                    //    item.LoadImage();
                }
            }

            //if (visibleTiles.Count == 0)
            //    throw new ApplicationException("Cos poszlo nie tak z przeliczaniem widocznosci");

            if (needCalculate)
                CalculateVisibleParts(vt, toRecalculate);
        }

        private void CheckTile(int x, int y, PointD pointD, List<Point> usedPoints, List<MapTile> toRecalculate, ref bool needCalculate, MapTile item)
        {
            Point point = new Point(x, y);
            if (!usedPoints.Contains(point) && this.tiles.Find(i => i.Middle.Equals(point)) == null)
            {
                MapTile newPart = new MapTile(new Size(256, 256), projectionCalculator.FromPoint(pointD, currentZoomLevel), currentZoomLevel, currentMapType, imgManager) { Middle = point };
                this.tiles.Add(newPart);
                toRecalculate.Add(newPart);
                usedPoints.Add(point);
                needCalculate = true;
            }
        }

        #endregion

        private void CheckVisible(MapTile tile)
        {
            // warunek do poprawki dla Y
            tile.IsVisible = (tile.Area.Location.X < map.Width && tile.Area.Location.Y + 256 > 0 && tile.Area.Location.X + 256 > 0 && tile.Area.Location.Y < map.Height)
                || (map.Width < tile.Size.Width && map.Height < tile.Size.Height && tile.Area.Location.X < 0 && tile.Area.Location.X + tile.Size.Width > map.Width && tile.Area.Location.Y < 0 && tile.Area.Location.Y + tile.Size.Height > map.Height);

            if (tile.IsVisible)
            {
                // wzgledem mapki-user controla
                int x = 0;
                int xv = 0;
                int y = 0;
                int yv = 0;

                if (tile.Area.Location.X < 0)
                {
                    xv = -1 * tile.Area.Location.X;
                }

                if (tile.Area.Location.X > 0)
                {
                    xv = 0;
                    x = tile.Area.Location.X;
                }

                if (tile.Area.Location.Y < 0)
                {
                    yv = -1 * tile.Area.Location.Y;
                }

                if (tile.Area.Location.Y > 0)
                {
                    yv = 0;
                    y = tile.Area.Location.Y;
                }

                // wzgledem obrazka mapki
                //int xv = item.Region.Location.X < 0 ? -1 * item.Region.Location.X : 0;
                //int yv = item.Region.Location.Y > map.Height ? 256 - (item.Region.Location.Y - map.Height) : 0;

                int width = tile.Size.Width;
                int height = tile.Size.Height;

                if (tile.Area.Location.X < 0)
                    width = tile.Size.Width + tile.Area.Location.X;

                if (tile.Size.Width <= map.Width && tile.Area.Location.X + tile.Size.Width > map.Width)
                    width = map.Width - tile.Area.Location.X;

                if (tile.Size.Width > map.Width && tile.Area.Location.X + tile.Size.Width > map.Width)
                    width = width - (tile.Size.Width + tile.Area.Location.X - map.Width);


                if (tile.Area.Location.Y < 0)
                    height = tile.Size.Height + tile.Area.Location.Y;
                if (tile.Size.Height <= map.Height && y + tile.Size.Height > map.Height)
                    height = map.Height - tile.Area.Location.Y;
                if (tile.Size.Height > map.Height && y + tile.Size.Height > map.Height)
                    height = height - (tile.Size.Height + tile.Area.Location.Y - map.Height);

                tile.VisibleAreaOnMap = new Rectangle(x, y, width, height);
                tile.VisibleAreaOfImage = new Rectangle(xv, yv, width, height);
            }
            else
                tile.VisibleAreaOfImage = new Rectangle();
        }

        #region IDisposable Members

        public void Dispose()
        {
            if (tiles != null && tiles.Count > 0)
            {
                foreach (var item in tiles)
                {
                    item.Dispose();
                }
            }
        }

        #endregion
    }
}
